import os
import paramiko
from scp import SCPClient
from datetime import datetime
import subprocess
import argparse

current_code_version = 'UNKNOWN'
need_trans_file = []
exclude_dir = ['.vscode']
update_file_mode = 0
fw_comments = ''

def get_code_version(svn_work_dir):
    global current_code_version
    try:
        # 构造安全命令参数列表（避免路径空格问题）
        cmd = ['svn', 'info', '--show-item', 'revision', svn_work_dir.strip()]
        
        # 执行命令并捕获输出
        result = subprocess.run(
            cmd,
            check=True,          # 非零退出码时触发异常[5](@ref)
            stdout=subprocess.PIPE,
            stderr=subprocess.PIPE,
            text=True,           # 自动解码为字符串[5](@ref)
            timeout=15           # 防止命令卡死[4](@ref)
        )
        
        # 验证输出有效性
        if result.stdout and result.stdout.strip().isdigit():
            current_code_version = result.stdout.strip()
            print(f"当前代码版本: {current_code_version}")
            return 0
        else:
            print("错误: 未获取到有效版本号")
            return None
            
    except subprocess.CalledProcessError as e:
        print(f"命令执行失败（状态码 {e.returncode}）: {e.stderr.strip()}")
    except FileNotFoundError:
        print("错误: 未找到 SVN 客户端，请安装 TortoiseSVN 或配置环境变量[2](@ref)")
    except PermissionError:
        print(f"权限错误: 无法访问路径 {path}")
    except Exception as e:
        print(f"未知错误: {str(e)}")
    
    return None

def get_svn_changes(svn_work_dir):
    """获取变更文件列表及对应操作"""
    result = subprocess.run(
        ['svn', 'status', svn_work_dir],
        capture_output=True, text=True, encoding='utf-8'
    )
    
    changes = []
    for line in result.stdout.split('\n'):
        if len(line) > 8:
            status = line[0]
            path = line[8:].strip()
            if status in ['M', 'A', 'D', '!', '?']:
                changes.append((status, path))
    return changes  # 示例返回：[('M', 'src/main.py'), ('A', 'docs/new.txt')]
    
def process_changes(svn_work_dir):
    global need_trans_file
    changes = get_svn_changes(svn_work_dir)
    for status, path in changes:
        try:
            if status == 'M' or status == 'A':
                file_path = os.path.relpath(path, svn_work_dir)
                need_trans_file.append(file_path.replace('\\', '/'))
        except Exception as e:
            print(f"处理失败: {path}\n{str(e)}")
    get_code_version(svn_work_dir)
    print("修改的文件:")
    if len(need_trans_file) == 0:
        print("无文件变动")
    else:
        for file in need_trans_file:
            print(file)
    




class SSHFileTransfer:
    def __init__(self, host, username, password=None, key_path=None, port=22):
        """
        初始化SSH连接
        :param host: 服务器IP地址
        :param username: 用户名
        :param password: 密码（可选）
        :param key_path: 密钥路径（可选）
        """
        self.host = host
        self.port = port
        self.username = username
        self.password = password
        self.key_path = key_path
        
        # 创建SSH客户端[3,9](@ref)
        self.ssh = paramiko.SSHClient()
        self.ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
        
        try:
            if key_path:  # 密钥认证[8,9](@ref)
                private_key = paramiko.RSAKey.from_private_key_file(key_path)
                self.ssh.connect(host, port, username, pkey=private_key)
            else:  # 密码认证[3](@ref)
                self.ssh.connect(host, port, username, password)
                
            self.scp = SCPClient(self.ssh.get_transport())
            self.sftp = self.ssh.open_sftp()
            print(f"[{datetime.now()}] 成功连接至 {host}")
            
        except paramiko.AuthenticationException:
            raise Exception("认证失败，请检查凭证")
        except paramiko.SSHException as e:
            raise Exception(f"SSH连接错误: {str(e)}")
    def get_dir_from_remote(self, local_dir, remote_dir):
        global exclude_dir
        os.makedirs(local_dir, exist_ok=True)
        # 递归遍历远程目录
        for entry in self.sftp.listdir_attr(remote_dir):
            remote_path = f"{remote_dir}/{entry.filename}"
            local_path = os.path.join(local_dir, entry.filename)
            
            if entry.st_mode & 0o40000:  # 如果是目录
                if entry.filename in exclude_dir:
                    continue
                os.makedirs(local_path, exist_ok=True)
                self.get_dir_from_remote(local_path, remote_path)
            else:
                self.sftp.get(remote_path, local_path)
                print(f"下载: {remote_path} -> {local_path}")

    def trans_code_to_remote(self, local_dir, remote_dir):
        global need_trans_file
        process_changes(local_dir)
        self.copy_directory_to_remote('l2r', local_dir, remote_dir, need_trans_file)


    def copy_directory_to_remote(self, direction, local_dir, remote_dir, exclude_files):
        """
        递归拷贝本地目录到远程服务器
        :param local_dir: 本地目录路径
        :param remote_dir: 远程目录路径
        """
        global exclude_dir
        try:
            if direction == 'l2r':
                work_dir = local_dir
                self.ssh.exec_command(f"mkdir -p {remote_dir}")
            else:
                work_dir = remote_dir
                if not os.path.exists(local_dir):
                    os.makedirs(local_dir)
                 
            # 遍历本地目录[1,3](@ref)
            print("文件同步:")
            if update_file_mode == 1 and len(exclude_files) <= 0:
                print("增量更新模式，无文件修改，无需同步")
                return
            for root, dirs, files in os.walk(work_dir):
                for dir_name in dirs:
                    if dir_name in exclude_dir:
                        continue
                    need_path = os.path.join(root, dir_name)
                    if direction == 'l2r':
                        create_path = os.path.join(remote_dir, os.path.relpath(need_path, work_dir))
                    else:
                        create_path = os.path.join(local_dir, os.path.relpath(need_path, work_dir))
                    create_path = create_path.replace('\\', '/')
                    #print("sync dir ->", remote_path)
                    #print("create path:", create_path)
                    if direction == 'l2r':
                        self.ssh.exec_command(f"mkdir -p {create_path}")
                    else:
                        if not os.path.exists(create_path):
                            os.makedirs(create_path)
                        
                    
                for file_name in files:
                    need_file_path = os.path.join(root, file_name)
                    if direction == 'l2r':
                        create_file_path = os.path.join(remote_dir, os.path.relpath(need_file_path, work_dir))
                        create_file_path = create_file_path.replace('\\', '/')
                        rel_path = os.path.relpath(create_file_path, remote_dir)
                    else:
                        create_file_path = os.path.join(local_dir, os.path.relpath(need_file_path, work_dir))
                        create_file_path = create_file_path.replace('\\', '/')
                        rel_path = os.path.relpath(create_file_path, local_dir)
                    rel_path = rel_path.replace('\\', '/')
                    if os.path.dirname(rel_path) in exclude_dir:
                        continue
                    if update_file_mode == 1 and exclude_files:
                        if rel_path not in exclude_files:
                            continue
                    #print("sync file ->", rel_path.replace('\\', '/'))
                    if direction == 'l2r':
                        self.scp.put(need_file_path, create_file_path)
                        print(f"[{datetime.now()}] trans: {need_file_path} -> {create_file_path}")
                    elif direction == 'r2l':
                        self.scp.get(need_file_path, create_file_path)
                        print(f"[{datetime.now()}] trans: {need_file_path} -> {create_file_path}")
                    
        except Exception as e:
            print(f"文件传输失败: {str(e)}")
    
    def trans_file_remote(self, src_path, dest_path):
        self.scp.put(src_path, dest_path)
        
    def get_file_from_remote(self, src_path, dest_path):
        self.scp.get(src_path, dest_path)

    def execute_command(self, command):
        """
        执行远程命令
        :param command: Linux命令
        :return: 命令输出
        """
        try:
            stdin, stdout, stderr = self.ssh.exec_command(command)
            output = stdout.read().decode()
            error = stderr.read().decode()
            
            if error:
                print(f"[ERROR] {error.strip()}")
            return output.strip()
            
        except paramiko.SSHException as e:
            print(f"命令执行失败: {str(e)}")
            return None

    def close(self):
        """关闭连接"""
        self.scp.close()
        self.ssh.close()
        print(f"[{datetime.now()}] 连接已关闭")

if __name__ == "__main__":
    # 配置参数[3,9](@ref)
    config = {
        "host": "172.17.27.19",
        "username": "root",
        "password": "sage123",
        "local_dir": "C:\\WIN11_OS_BACKUP\\serpensv2_fw\\RAM\\trunk\\source", #本地代码路径
        "remote_dir": "/home/hwc/syncdir/source", #远端代码路径
        "download_dir": "C:\\WIN11_OS_BACKUP\\FW", #远端文件回传路径
        "local_bootloader_dir": "C:\\WIN11_OS_BACKUP\\serpensv2_fw\\RAM\\branches\\bootloader"
    }
    if len(config["download_dir"]) > 0:
        if not os.path.exists(config["download_dir"]):
            os.makedirs(config["download_dir"])
    parser = argparse.ArgumentParser(description='命令行参数解析')
    parser.add_argument('--inc', type=int, default=0, help='有此参数表示增量更新文件')
    parser.add_argument('--desc', type=str, default='', help='对固件的描述')
    parser.add_argument('--mode', type=str, default='upgrade', help='编译产物:part0/upgrade/release')
    parser.add_argument('-u', '--update', help="表示只更新全部文件，不编译", action="store_true")
    parser.add_argument('--code', type=str, default='', help="本地代码路径")
    parser.add_argument('--download', type=str, default='', help="本地下载路径")
    parser.add_argument('--cloneenv', type=str, default='', help="本地下载路径")
    args = parser.parse_args()
    update_file_mode = args.inc
    fw_comments = args.desc
    transfer = None
    if args.download and len(args.download) > 0:
        config['download_dir'] = args.download
    if args.code and len(args.code) > 0:
        config['local_dir'] = args.code
        
    try:
        # 初始化连接
        transfer = SSHFileTransfer(
            host=config["host"],
            username=config["username"],
            password=config.get("password"),
            key_path=config.get("key_path")
        )
        
        # 示例操作
        print("服务器信息:")
        print(transfer.execute_command("uname -a"))  # 执行命令[10](@ref)
        
        if args.cloneenv and len(args.cloneenv) > 0:
            print(transfer.execute_command(f"mkdir -p {args.cloneenv} && cp -rf /code-build/demo/* {args.cloneenv} && echo 'CLONE ENV SUCCESS'"))
            exit()
        
        if args.update:
            transfer.trans_code_to_remote(config["local_dir"], config["remote_dir"])  # 同步代码
            if config['local_bootloader_dir'] and len(config['local_bootloader_dir']) > 0:
                bootloader_path = os.path.join(os.path.dirname(config["remote_dir"]), 'bootloader')
                transfer.trans_code_to_remote(config["local_bootloader_dir"], bootloader_path)
            exit()
        
        # 验证传输
        print(transfer.execute_command(f"cd {config['remote_dir']}/../ && rm -rf build && mkdir -p FW"))
        if args.mode == 'part0':
            # bootloader代码路径存在则先编译bootloader固件
            if config['local_bootloader_dir'] and len(config['local_bootloader_dir']) > 0:
                bootloader_path = os.path.join(os.path.dirname(config["remote_dir"]), 'bootloader')
                bootloader_path = bootloader_path.replace('\\', '/')
                print(transfer.execute_command(f"cd {bootloader_path}/../combine && rm -f bootload_ram.bta"))
                transfer.trans_code_to_remote(config["local_bootloader_dir"], bootloader_path)
                print("代码编译:")
                print(transfer.execute_command(f"cd {bootloader_path} && rm -rf ./build && make -f Makefile && cp ./bootload_ram.bta {config['remote_dir']}/../combine/"))
            execute_file_name = datetime.now().strftime(f"EXP-FW-PART0-%m%d-%H%M%S.bta") if len(fw_comments) <= 0 \
                                else datetime.now().strftime(f"EXP-FW-PART0-%m%d-%H%M%S-{fw_comments}.bta")
            print(transfer.execute_command(f"cd {config['remote_dir']}/../combine && cp ./script/CombineScript_part0.ini ./CombineScript.ini && rm -f ./exp_part0.bta && ./CombineCode"))
            print(transfer.execute_command(f"cd {config['remote_dir']}/../combine && cp ./exp_part0.bta ../FW/{execute_file_name}"))
            if len(config["download_dir"]) > 0:
                src_path = os.path.join(os.path.dirname(config["remote_dir"]), f'FW/{execute_file_name}')
                src_path = src_path.replace('\\', '/')
                transfer.get_file_from_remote(src_path, config["download_dir"])
                if os.path.exists(os.path.join(config['download_dir'], execute_file_name)):
                    print(f"DOWNLOAD SUCCESS: {config['download_dir']}\\{execute_file_name}")
                else:
                    print(f"DOWNLOAD FAILURE!!!")
        elif args.mode =='upgrade':
            transfer.trans_code_to_remote(config["local_dir"], config["remote_dir"])  # 同步代码
            print("代码编译:")
            print(transfer.execute_command(f"cd {config['remote_dir']}/../combine && cp ./script/CombineScript_upgrade.ini ./CombineScript.ini"))
            execute_file_name = datetime.now().strftime(f"EXP-FW-SVN{current_code_version}-%m%d-%H%M%S.bta") if len(fw_comments) <= 0 \
                                else datetime.now().strftime(f"EXP-FW-SVN{current_code_version}-%m%d-%H%M%S-{fw_comments}.bta")
            print(transfer.execute_command(f'''cd {config["remote_dir"]}/../ && make -f Makefile &&
                cp ./combine/combineData_upgrade.bta ./FW/{execute_file_name} &&
                echo "CODE FILE: {execute_file_name}"'''))
            if len(config["download_dir"]) > 0:
                src_path = os.path.join(os.path.dirname(config["remote_dir"]), "FW", execute_file_name)
                src_path = src_path.replace('\\', '/')
                transfer.get_file_from_remote(src_path, config["download_dir"])
                print(f"DOWNLOAD SUCCESS: {config['download_dir']}\\{execute_file_name}")
        elif args.mode == 'release':
            transfer.trans_code_to_remote(config["local_dir"], config["remote_dir"])  # 同步代码
            print(transfer.execute_command(f"cd {config['remote_dir']}/../ && make -f Makefilec2olib"))
            if len(config["download_dir"]) > 0:
                transfer.get_dir_from_remote(os.path.join(config["download_dir"], 'source'), config["remote_dir"])
                print(f"DOWNLOAD SUCCESS: {config['download_dir']}\\{os.path.basename(config['remote_dir'])}")
        
    except Exception as e:
        print(f"操作失败: {str(e)}")
    finally:
        if transfer:
            transfer.close()